package pers.lonestar.framework.utils.support.write;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.slf4j.Logger;

import net.sf.cglib.beans.BeanMap;
import pers.lonestar.framework.utils.support.annotations.ExcelColumnProperty;
import pers.lonestar.framework.utils.support.constants.ExcelXmlConstants;
import pers.lonestar.framework.utils.support.contexts.XlsxWriteContext;
import pers.lonestar.framework.utils.support.converters.Converter;
import pers.lonestar.framework.utils.support.converters.DefaultConverter;
import pers.lonestar.framework.utils.support.enums.CellDataTypeEnum;
import pers.lonestar.framework.utils.support.metadata.CellData;
import pers.lonestar.framework.utils.support.metadata.RowData;
import pers.lonestar.framework.utils.support.metadata.write.WriteSheet;
import pers.lonestar.framework.utils.support.metadata.write.WriteWorkbook;
import pers.lonestar.framework.utils.support.utils.ConverterUtil;
import pers.lonestar.framework.utils.support.utils.FieldUtil;
import pers.lonestar.framework.utils.support.utils.StringUtil;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public class ExcelWriter {
    private static final Logger log = org.slf4j.LoggerFactory.getLogger(ExcelWriter.class);
    private final XlsxWriteContext writeContext;

    public ExcelWriter(WriteWorkbook writeWorkbook) {
        writeContext = new XlsxWriteContext();
        writeContext.setWriteWorkbook(writeWorkbook);
    }

    public Mono<Void> write(WriteSheet... writeSheet) {
        return write(Arrays.asList(writeSheet));
    }

    public Mono<Void> write(List<WriteSheet> writeSheetList) {
        return getSortedWriteSheetFlux(writeSheetList)
                .flatMap(writeSheet -> {
                    WriteWorkbook writeWorkbook = writeContext.getWriteWorkbook();
                    Workbook workbook = writeWorkbook.getWorkbook();
                    Sheet sheet;
                    //未给定则使用默认名
                    if (!StringUtil.isEmpty(writeSheet.getSheetName())) {
                        sheet = workbook.createSheet(writeSheet.getSheetName());
                    } else {
                        sheet = workbook.createSheet();
                        writeSheet.setSheetName(sheet.getSheetName());
                    }
                    writeSheet.setSheetIndex(workbook.getSheetIndex(sheet));
                    writeSheet.setCurrentSheet(sheet);
                    writeContext.setWriteSheet(writeSheet);
                    return Flux.concat(
                            createHeadRowDataFlux(writeWorkbook, writeSheet),
                            createContentRowDataFlux(writeWorkbook, writeSheet));
                }).doOnNext(this::writeRowData)
                .doFinally(signalType -> {
                    //TODO
                    try {
                        finish();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                })
                .then();
    }

    private void writeRowData(RowData rowData) {
        WriteSheet writeSheet = rowData.getWriteSheet();
        WriteWorkbook writeWorkbook = rowData.getWriteWorkbook();
        Workbook workbook = writeWorkbook.getWorkbook();
        int currentRowIndex = writeSheet.getCurrentRowIndex();
        //测试分页
        if (currentRowIndex >= ExcelXmlConstants.XLSX_MAX_ROW_INDEX) {
            if (log.isDebugEnabled()) {
                log.debug("数据量达到当前页最大行数限制，进行分页操作，创建新sheet页");
            }
            if (writeSheet.getSheetNumber() == 1) {
                if (log.isDebugEnabled()) {
                    log.debug("修改所属首页名为：{}", writeSheet.getSheetName() + "-" + writeSheet.getSheetNumber());
                }
                workbook.setSheetName(writeSheet.getSheetIndex(), writeSheet.getSheetName() + "-" + writeSheet.getSheetNumber());
            }
            writeSheet.setSheetNumber(writeSheet.getSheetNumber() + 1);
            if (log.isDebugEnabled()) {
                log.debug("建立新分页名为：{}", writeSheet.getSheetName() + "-" + writeSheet.getSheetNumber());
            }
            Sheet sheet = workbook.createSheet(writeSheet.getSheetName() + "-" + writeSheet.getSheetNumber());
            writeSheet.setCurrentSheet(sheet);
            writeSheet.setCurrentRowIndex(0);
            createHeadRowDataFlux(writeWorkbook, writeSheet).subscribe(this::writeRowData);
        }
        rowData.getCellDataList().forEach(cellData -> {
            cellData.setRowIndex(writeSheet.getCurrentRowIndex());
            writeCellData(cellData);
        });
        writeSheet.setCurrentRowIndex(writeSheet.getCurrentRowIndex() + 1);
    }

    private void writeCellData(CellData cellData) {
        Sheet sheet = writeContext.getWriteSheet().getCurrentSheet();
        int rowIndex = cellData.getRowIndex();
        Row row = sheet.getRow(rowIndex);
        if (row == null) {
            row = sheet.createRow(rowIndex);
        }
        int columnIndex = cellData.getColumnIndex();
        Cell poiCell = row.getCell(columnIndex);
        if (poiCell == null) {
            poiCell = row.createCell(columnIndex);
        }
        poiCell.setCellStyle(cellData.getCellStyle());
        switch (cellData.getType()) {
            case STRING:
                poiCell.setCellValue(cellData.getStringValue());
                break;
            case NUMBER:
            case DATE:
                poiCell.setCellValue(cellData.getNumberValue().doubleValue());
                break;
            case BOOLEAN:
                poiCell.setCellValue(cellData.getBooleanValue());
                break;
            default:
                break;
        }
    }

    /**
     * 创建表头行数据流
     *
     * @param writeWorkbook 写入工作簿
     * @param writeSheet    写入工作表
     * @return 表头行数据流
     */
    private Flux<RowData> createHeadRowDataFlux(WriteWorkbook writeWorkbook, WriteSheet writeSheet) {
        if (!writeSheet.isHasHead()) {
            return Flux.empty();
        }
        List<String> headTitleList = writeSheet.getHeadTitleList();
        Class<?> headClass = writeSheet.getHeadClass();
        //优先使用传入的HeadNameList作为表头构建参数
        if (headTitleList != null && !headTitleList.isEmpty()) {
            return Flux.create(fluxSink -> {
                RowData rowData = new RowData();
                rowData.setRowIndex(0);
                rowData.setWriteWorkbook(writeWorkbook);
                rowData.setWriteSheet(writeSheet);
                List<CellData<?>> cellDataList = rowData.getCellDataList();
                for (int columnIndex = 0; columnIndex < headTitleList.size(); columnIndex++) {
                    CellData<String> cellData = new CellData<>();
                    cellData.setStringValue(headTitleList.get(columnIndex));
                    cellData.setType(CellDataTypeEnum.STRING);
                    cellData.setRowIndex(0);
                    cellData.setColumnIndex(columnIndex);
                    cellData.setCellStyle(writeWorkbook.getHeadCellStyle());
                    cellDataList.add(cellData);
                }
                fluxSink.next(rowData);
                fluxSink.complete();
            });
        } else if (headClass != null) {
            List<Field> fieldList = writeSheet.getFieldList();
            if (fieldList.isEmpty()) {
                return Flux.empty();
            }
            return Flux.create(fluxSink -> {
                RowData rowData = new RowData();
                rowData.setRowIndex(0);
                rowData.setWriteWorkbook(writeWorkbook);
                rowData.setWriteSheet(writeSheet);
                List<CellData<?>> cellDataList = rowData.getCellDataList();
                for (int columnIndex = 0; columnIndex < fieldList.size(); columnIndex++) {
                    Field field = fieldList.get(columnIndex);
                    String fieldName;
                    ExcelColumnProperty fieldProperty = field.getAnnotation(ExcelColumnProperty.class);
                    if (fieldProperty != null) {
                        fieldName = fieldProperty.description();
                    } else {
                        fieldName = field.getName();
                    }
                    CellData<String> cellData = new CellData<>();
                    cellData.setStringValue(fieldName);
                    cellData.setType(CellDataTypeEnum.STRING);
                    cellData.setRowIndex(0);
                    cellData.setColumnIndex(columnIndex);
                    cellData.setCellStyle(writeWorkbook.getHeadCellStyle());
                    cellDataList.add(cellData);
                }
                fluxSink.next(rowData);
                fluxSink.complete();
            });
        } else {
            return Flux.empty();
        }
    }

    private Flux<RowData> createContentRowDataFlux(WriteWorkbook writeWorkbook, WriteSheet writeSheet) {
        //若 headFieldList 不为空，则代表是明确传参指定的属性列表，优先使用
        if (writeSheet.getHeadClass() != null) {
            return writeSheet.getDataFlux()
                    .index()
                    .map(indexData -> {
                        int rowIndex = indexData.getT1().intValue() + 1;
                        Object data = indexData.getT2();
                        return getContentRowDataByHeadClass(data, writeWorkbook, writeSheet, rowIndex);
                    });
        } else if (writeSheet.getHeadNameList() != null && !writeSheet.getHeadNameList().isEmpty()) {
            List<String> headNameList = writeSheet.getHeadNameList();
            return writeSheet.getDataFlux()
                    .index()
                    .map(indexData -> {
                        int rowIndex = indexData.getT1().intValue() + 1;
                        Object data = indexData.getT2();
                        return getContentRowDataByHeadFieldList(headNameList, data, writeWorkbook, writeSheet, rowIndex);
                    });
        } else {
            return writeSheet.getDataFlux()
                    .index()
                    .map(indexData -> {
                        int rowIndex = indexData.getT1().intValue() + 1;
                        Object data = indexData.getT2();
                        return getContentRowDataByDataType(data, writeWorkbook, writeSheet, rowIndex);
                    });
        }
    }

    private Flux<WriteSheet> getSortedWriteSheetFlux(List<WriteSheet> writeSheetList) {
        return Flux.fromIterable(writeSheetList)
                .map(writeSheet -> {
                    if (writeSheet.getSheetIndex() == null) {
                        writeSheet.setSheetIndex(Integer.MAX_VALUE);
                    }
                    return writeSheet;
                })
                .sort(Comparator.comparingInt(WriteSheet::getSheetIndex))
                .index()
                .map(writeSheetTuple -> {
                    int sheetIndex = writeSheetTuple.getT1().intValue();
                    WriteSheet writeSheet = writeSheetTuple.getT2();
                    writeSheet.setSheetIndex(sheetIndex);
                    //若未给定则创建时使用默认sheet名
                    if (StringUtil.isEmpty(writeSheet.getSheetName())) {
                        writeSheet.setSheetName("Sheet" + (sheetIndex + 1));
                    }
                    return writeSheet;
                });
    }

    private RowData getContentRowDataByHeadFieldList(List<String> headFieldList, Object data, WriteWorkbook writeWorkbook, WriteSheet writeSheet, Integer rowIndex) {
        RowData rowData = new RowData();
        rowData.setRowIndex(rowIndex);
        rowData.setWriteWorkbook(writeWorkbook);
        rowData.setWriteSheet(writeSheet);
        List<CellData<?>> cellDataList = rowData.getCellDataList();
        boolean isMap = data instanceof Map;
        BeanMap beanMap = null;
        if (!isMap) {
            beanMap = BeanMap.create(data);
        }
        for (int columnIndex = 0; columnIndex < headFieldList.size(); columnIndex++) {
            CellData cellData;
            if (isMap) {
                cellData = ConverterUtil.convertToCellData(((Map) data).get(headFieldList.get(columnIndex)));
            } else {
                cellData = ConverterUtil.convertToCellData(beanMap.get(headFieldList.get(columnIndex)));
            }
            cellData.setRowIndex(rowIndex);
            cellData.setColumnIndex(columnIndex);
            cellData.setCellStyle(writeWorkbook.getContentCellStyleMap().get(cellData.getType()));
            cellDataList.add(cellData);
        }
        return rowData;
    }

    private RowData getContentRowDataByHeadClass(Object data, WriteWorkbook writeWorkbook, WriteSheet writeSheet, Integer rowIndex) {
        Class<?> headClass = writeSheet.getHeadClass();
        List<Field> fieldList = writeSheet.getFieldList();
        List<String> headNameList = writeSheet.getHeadNameList();
        List<? extends Converter> converterList = writeSheet.getConverterList();
        if (headClass == null) {
            headClass = data.getClass();
            fieldList = FieldUtil.getSortedDeclaredFieldList(headClass);
            headNameList = FieldUtil.getSortedDeclaredFieldNameList(fieldList);
            converterList = FieldUtil.getSortedDeclaredFieldConverterList(fieldList);
            writeSheet.setHeadClass(headClass);
            writeSheet.setFieldList(fieldList);
            writeSheet.setHeadNameList(headNameList);
            writeSheet.setConverterList(converterList);
        }
        boolean isMap = data instanceof Map;
        BeanMap beanMap = null;
        if (!isMap) {
            beanMap = BeanMap.create(data);
        }
        RowData rowData = new RowData();
        rowData.setRowIndex(rowIndex);
        rowData.setWriteWorkbook(writeWorkbook);
        rowData.setWriteSheet(writeSheet);
        List<CellData<?>> cellDataList = rowData.getCellDataList();
        for (int columnIndex = 0; columnIndex < fieldList.size(); columnIndex++) {
            Converter converter = converterList.get(columnIndex);
            CellData cellData;
            if (isMap) {
                cellData = ConverterUtil.convertToCellData(((Map) data).get(headNameList.get(columnIndex)));
            } else {
                //Filed对应Converter为null或DefaultConverter表示未自定义字段转换方式，则根据字段类型进行转换
                if (converter == null || converter instanceof DefaultConverter) {
                    cellData = ConverterUtil.convertToCellData(beanMap.get(headNameList.get(columnIndex)));
                }
                //Converter非空，使用用户自定义Converter进行转换
                else {
                    cellData = converter.convertToCellData(beanMap.get(headNameList.get(columnIndex)));
                }
            }
            cellData.setRowIndex(rowIndex);
            cellData.setColumnIndex(columnIndex);
            cellData.setCellStyle(writeWorkbook.getContentCellStyleMap().get(cellData.getType()));
            cellDataList.add(cellData);
        }
        return rowData;
    }

    private RowData getContentRowDataByDataType(Object data, WriteWorkbook writeWorkbook, WriteSheet writeSheet, Integer rowIndex) {
        if (data instanceof Map) {
            return getContentRowDataByHeadFieldList(new ArrayList<>(((Map) data).keySet()), data, writeWorkbook, writeSheet, rowIndex);
        } else {
            return getContentRowDataByHeadClass(data, writeWorkbook, writeSheet, rowIndex);
        }
    }

    private void finish() throws IOException {
        WriteWorkbook writeWorkbook = writeContext.getWriteWorkbook();
        Workbook workbook = writeWorkbook.getWorkbook();
        File file = writeWorkbook.getFile();
        if (file != null) {
            workbook.write(new FileOutputStream(file));
            workbook.close();
            return;
        }
        OutputStream outputStream = writeWorkbook.getOutputStream();
        if (outputStream != null) {
            workbook.write(outputStream);
            workbook.close();
            return;
        }
    }
}
